/*
 * ethernet.c
 *
 *  Created on: Oct 24, 2013
 *      Author: Ken Arok
 */

#include "config_peripherals.h"

#if ETHERNET_PERIPHERAL_ACTIVE

#include <string.h>

/* Scheduler include files. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"


/* lwIP includes */
#include "lwip/sys.h"
#include "lwip/api.h"
#include "lwip/tcpip.h"
#include "lwip/memp.h"
#include <lwip/dhcp.h>
#include <lwip/dns.h>
#include <lwip/netif.h>
#include "lwip/stats.h"
#include "lwip/init.h"
#if ((LWIP_VERSION) == ((1U << 24) | (3U << 16) | (2U << 8) | (LWIP_VERSION_RC)))
#include "netif/loopif.h"
#else
#include "lwip/inet.h"
#endif

#include "config_ROM_HW.h"
#include "endian/endian.h"


/* Ethernet includes */
#include "config_eth.h"
#include "macb.h"
#include "macaddr.h"
#include "rom.h"
#include "ethernet.h"


/* Boot Task stack size. */
#define ETHERNETTASK_STACK_SIZE			64

/* Boot Task priority. */
#define ETHERNETTASK_PRIORITY			(configMAX_PRIORITIES)


/* Network Link Wakeup Signaling. */
xSemaphoreHandle xNetLinkUpSignaling = NULL, xNetLinkDownSignaling = NULL;

/* Structure of Network Interface. */
struct netif network_if;

/* Array of IP address character. */
static char cIPAddress[16];
/* Array of MAC address character. */
static char cMACAddress[6];

/* Network status. */
static uint8_t NetworkLinkStatus = 0;

/* Flag for restarting Network immediately. */
static bool RestartNetworkImmediate = false;

/* Ethernet Configuration. */
static bool autoNetwork;
static uint8_t ipAddr[4];
static uint8_t subNet[4];
static uint8_t gateWay[4];

static uint8_t *_ptrBase;


/** \brief Ethernet Task.
 *
 */
static portTASK_FUNCTION(pvEthernetTask, pvParameters);

/** \brief Restart Ethernet.
 *
 */
static void eth_restart(void);

/** \brief status callback used to print address given by DHCP.
 *
 * \param _netif	Pointer to structure of network interface.
 *
 */
static void status_callback(struct netif *_netif);

/** \brief Configure the Ethernet interface.
 */
static void eth_conf_interface(void);

/** \brief Callback executed when the TCP/IP init is done.
 *
 * \param _arg		Pointer to the argument for call back.
 *
 */
static void tcpip_init_done(void *_arg);

/** \brief Initialize lwIP layer.
 */
static void lwIPInit(void);


/* --------------------------------------------------------------------------- */

void vEthernetInit(void)
{
	/* Initialize ROM base address pointer. */
	if(ROM_Base_Block1 == NULL) {
		/* Prepare Copy Block 1 of ROM to RAM. */
		ROM_Base_Block1 = pvPortMalloc(ROM_BLOCK_SIZE);
		if(ROM_Base_Block1 == NULL) {
			while(1);
		}

		if(vReadROM(ROM_BASE_ADDR_BLOK1, ROM_Base_Block1, ROM_BLOCK_SIZE)) {
			while(1);
		}
	}

	_ptrBase = ROM_Base_Block1;

	/* Load Ethernet setting. */
	vEthernetCtrl(ETH_LOAD_SETTING_FROM_ROM, NULL);

	/* Create Ethernet Task. */
	xTaskCreate(pvEthernetTask, (const signed portCHAR *)"ETH", ETHERNETTASK_STACK_SIZE, NULL, ETHERNETTASK_PRIORITY, (xTaskHandle)NULL);
}


void *vEthernet_ReadIPAddress(void)
{
	return cIPAddress;
}


void *vEthernet_ReadMACAddress(void)
{
	return  cMACAddress;
}


uint8_t vEthernet_ReadNetworkStatus(void)
{
	return NetworkLinkStatus;
}


portBASE_TYPE vEthernetCtrl(eth_request_code_t reqCode, void *pValue)
{
	portBASE_TYPE _ret_status = pdPASS;

	switch(reqCode) {
		case ETH_RESTART_MODULE:
			RestartNetworkImmediate = true;
		break;

		case ETH_SET_DEFAULT:
			autoNetwork = true;
			ipAddr[0] = ETHERNET_CONF_IPADDR0;
			ipAddr[1] = ETHERNET_CONF_IPADDR1;
			ipAddr[2] = ETHERNET_CONF_IPADDR2;
			ipAddr[3] = ETHERNET_CONF_IPADDR3;
			subNet[0] = ETHERNET_CONF_NET_MASK0;
			subNet[1] = ETHERNET_CONF_NET_MASK1;
			subNet[2] = ETHERNET_CONF_NET_MASK2;
			subNet[3] = ETHERNET_CONF_NET_MASK3;
			gateWay[0] = ETHERNET_CONF_GATEWAY_ADDR0;
			gateWay[1] = ETHERNET_CONF_GATEWAY_ADDR1;
			gateWay[2] = ETHERNET_CONF_GATEWAY_ADDR2;
			gateWay[3] = ETHERNET_CONF_GATEWAY_ADDR3;
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_CONF_AUTOMANUAL_OFFSET), autoNetwork);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR0_OFFSET), ipAddr[0]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR1_OFFSET), ipAddr[1]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR2_OFFSET), ipAddr[2]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR3_OFFSET), ipAddr[3]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK0_OFFSET), subNet[0]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK1_OFFSET), subNet[1]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK2_OFFSET), subNet[2]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK3_OFFSET), subNet[3]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY0_OFFSET), gateWay[0]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY1_OFFSET), gateWay[1]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY2_OFFSET), gateWay[2]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY3_OFFSET), gateWay[3]);
		break;

		case ETH_LOAD_SETTING_FROM_ROM:
			autoNetwork = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_CONF_AUTOMANUAL_OFFSET));
			ipAddr[0] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR0_OFFSET));
			ipAddr[1] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR1_OFFSET));
			ipAddr[2] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR2_OFFSET));
			ipAddr[3] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR3_OFFSET));
			subNet[0] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK0_OFFSET));
			subNet[1] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK1_OFFSET));
			subNet[2] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK2_OFFSET));
			subNet[3] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK3_OFFSET));
			gateWay[0] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY0_OFFSET));
			gateWay[1] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY1_OFFSET));
			gateWay[2] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY2_OFFSET));
			gateWay[3] = BigEndian_LoadByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY3_OFFSET));
		break;

		case ETH_SAVE_SETTING_TO_ROM:
			/* Erase ROM Block. */
			vEraseROM(ROM_BASE_ADDR_BLOK1);
			/* Write ROM Block. */
			vWriteROM(ROM_BASE_ADDR_BLOK1, (const void *)ROM_Base_Block1, ROM_BLOCK_SIZE);
		break;

		case ETH_SET_IP_ADDR:
			memcpy((void *)ipAddr, (const void *)pValue, 4);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR0_OFFSET), ipAddr[0]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR1_OFFSET), ipAddr[1]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR2_OFFSET), ipAddr[2]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_IP_ADDR3_OFFSET), ipAddr[3]);
		break;

		case ETH_SET_SUBNET_MASK:
			memcpy((void *)subNet, (const void *)pValue, 4);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK0_OFFSET), subNet[0]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK1_OFFSET), subNet[1]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK2_OFFSET), subNet[2]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_SUBNET_MASK3_OFFSET), subNet[3]);
		break;

		case ETH_SET_GATEWAY:
			memcpy((void *)gateWay, (const void *)pValue, 4);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY0_OFFSET), gateWay[0]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY1_OFFSET), gateWay[1]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY2_OFFSET), gateWay[2]);
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_GATEWAY3_OFFSET), gateWay[3]);
		break;

		case ETH_SET_MODE:
			autoNetwork = *(bool *)pValue;
			BigEndian_StoreByte((void *)(_ptrBase + ROM_TCPIP_CONF_AUTOMANUAL_OFFSET), autoNetwork);
		break;

		default: break;
	}

	return _ret_status;
}

/* ------------------------------------------------------------------------------- */

static portTASK_FUNCTION(pvEthernetTask, pvParameters)
{
	bool _networkStatus;

	/* Setup lwIP. */
	lwIPInit();

	/* Set the priority of the current task to the lowest possible. */
	vTaskPrioritySet(NULL, tskIDLE_PRIORITY + 1);


	for(;;) {

		if(RestartNetworkImmediate == true) {
			/* Release flag. */
			RestartNetworkImmediate = false;
			/* Load setting. */
			vEthernetCtrl(ETH_LOAD_SETTING_FROM_ROM, NULL);
			/* Re-initialize network. */
			eth_restart();
			/* Signaling upper task. */
			xSemaphoreGive(xNetLinkUpSignaling);
		}

		_networkStatus = vMACBNetworkIsConnected(&AVR32_MACB);
		if(_networkStatus == false) {
			/* Re-initialize Network if this is new event. */
			if(NetworkLinkStatus == 2) {
				/* Re-initialize network. */
				eth_restart();
			}
		}
		else {
			/* Change Status. */
			NetworkLinkStatus = 2;
		}

		vTaskDelay(100);
	}
}


static void eth_restart(void)
{
	/* Change Status connecting. */
	NetworkLinkStatus = 1;
	/* Stop traffic. */
	netif_set_down(&network_if);
	/* Remove current netif. */
	netif_remove(&network_if);
	/* Set the priority of the current task to the default. */
	vTaskPrioritySet(NULL, ETHERNETTASK_PRIORITY);
	/* Setup lwIP. */
	lwIPInit();
	/* Change Status connected. */
	NetworkLinkStatus = 2;
	/* Set the priority of the current task to the lowest possible. */
	vTaskPrioritySet(NULL, tskIDLE_PRIORITY + 1);
}


static void status_callback(struct netif *_netif)
{
	if(netif_is_up(_netif)) {
		strcpy(cIPAddress, (const char *)inet_ntoa(*(struct in_addr *) & (_netif->ip_addr)));
	}
}


static void eth_conf_interface(void)
{
	struct ip_addr _xIpAddr, _xNetMask, _xGateway;
	extern err_t ethernetif_init(struct netif *_netif);

	/* Clear IP address buffer */
	memset((void *)cIPAddress, 0, 16);

	/* Read MAC hardware address. */
	macaddr_read((void *)cMACAddress);

	/* Pass the MAC address to MACB module */
	vMACBSetMACAddress((const unsigned char *)cMACAddress);

	if(autoNetwork == true) {
		_xIpAddr.addr  = 0;
		_xNetMask.addr = 0;
		_xGateway.addr = 0;
	}
	else {
		/* Setup IP addr. */
		IP4_ADDR(&_xIpAddr, ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]);

		/* Setup Subnet mask */
		IP4_ADDR(&_xNetMask, subNet[0], subNet[1], subNet[2], subNet[3]);

		/* Setup Gateway */
		IP4_ADDR(&_xGateway, gateWay[0], gateWay[1], gateWay[2], gateWay[3]);
	}

	/* add data to netif */
	netif_add(&network_if, &_xIpAddr, &_xNetMask, &_xGateway, NULL, ethernetif_init, tcpip_input);

	/* Make it the default interface */
	netif_set_default(&network_if);

	/* Setup callback function for netif status change */
	netif_set_status_callback(&network_if, status_callback);

	if(autoNetwork == true) {
		/* Bring it up */
		dhcp_start(&network_if);
	}
	else {
		/* Bring it up */
		netif_set_up(&network_if);
	}
}


static void tcpip_init_done(void *_arg)
{
	sys_sem_t *sem;
	sem = (sys_sem_t *)_arg;

	/* Set H/W and IP parameters, initialize MACB too */
	eth_conf_interface();

#if ((LWIP_VERSION) == ((1U << 24) | (3U << 16) | (2U << 8) | (LWIP_VERSION_RC)))
	sys_sem_signal(*sem); // Signal the waiting thread that the TCP/IP init is done.
#else
	sys_sem_signal(sem); // Signal the waiting thread that the TCP/IP init is done.
#endif
}


static void lwIPInit(void)
{
	sys_sem_t sem;

#if ((LWIP_VERSION) == ((1U << 24) | (3U << 16) | (2U << 8) | (LWIP_VERSION_RC)))
	sem = sys_sem_new(0); // Create a new semaphore.
	tcpip_init(tcpip_init_done, &sem);
	sys_sem_wait(sem);    // Block until the lwIP stack is initialized.
	sys_sem_free(sem);    // Free the semaphore.
#else
	err_t  err_sem;
	err_sem = sys_sem_new(&sem, 0); // Create a new semaphore.
	tcpip_init(tcpip_init_done, &sem);
	sys_sem_wait(&sem);    // Block until the lwIP stack is initialized.
	sys_sem_free(&sem);    // Free the semaphore.
#endif
}


#else /* ETHERNET_PERIPHERAL_ACTIVE */

char cIPAddress[1] = 0;
char cMACAddress[1] = 0;

#endif /* ETHERNET_PERIPHERAL_ACTIVE */


